package cn.tedu.knows.auth.security;

import cn.tedu.knows.auth.service.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.annotation.Resource;
import java.util.Arrays;

@Configuration
// 这个注解表示当前类是Oauth2标准下
// 实现授权服务器的配置类,启动授权服务器相关功能
@EnableAuthorizationServer
public class AuthorizationServer extends
        AuthorizationServerConfigurerAdapter {
    // 添加依赖注入
    // 授权管理器
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private UserDetailsServiceImpl userDetailsService;

    // 重写方法1
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer
                                  endpoints) throws Exception {
        // Oauth2标准提供了很多控制器的方法用于授权或验证
        // 这个方法的参数endpoints翻译为端点,这个端点就是Oauth2提供的控制器方法
        // 我们要配置端点的设置,以便授权或验证等操作顺利执行
        endpoints
                // 配置权限管理器对象
                .authenticationManager(authenticationManager)
                // 配置登录时获得用户详情的对象
                .userDetailsService(userDetailsService)
                // 配置登录时发访问控制器的方法
                .allowedTokenEndpointRequestMethods(HttpMethod.GET)
                // 配置生成令牌的对象
                .tokenServices(tokenService());
    }
    // 添加保存令牌的配置对象
    @Resource
    private TokenStore tokenStore;
    // 添加客户端详情依赖对象(系统自动提供)
    @Resource
    private ClientDetailsService clientDetailsService;
    @Resource
    private JwtAccessTokenConverter accessTokenConverter;
    // 配置生成和保存令牌的方法
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        // 创建生成令牌的对象
        DefaultTokenServices services=
                new DefaultTokenServices();
        // 设置令牌如何保存
        services.setTokenStore(tokenStore);
        //  ↓↓↓↓↓↓↓↓↓↓新增代码↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        // 实例化令牌增强对象
        TokenEnhancerChain chain=new TokenEnhancerChain();
        // 将JWT转换器设置到令牌增强对象中
        chain.setTokenEnhancers(
                Arrays.asList(accessTokenConverter));
        // 将令牌增强对象设置到令牌生成器中
        services.setTokenEnhancer(chain);
        // ↑↑↑↑↑↑↑↑↑↑↑↑↑↑新增代码结束↑↑↑↑↑↑↑↑↑↑↑

        // 设置令牌有效期(3600秒 1小时)
        services.setAccessTokenValiditySeconds(3600);
        // 配置客户端详情(这个令牌为哪个客户端生成)
        services.setClientDetailsService(clientDetailsService);
        // 别忘了返回services
        return services;
    }

    // 获得加密对象
    @Resource
    private PasswordEncoder passwordEncoder;

    // 重写方法2
    // 配置当前服务器授权的客户端信息
    // 这里的客户端指所有依赖当前授权服务进行授权\登录的项目
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 当前项目只需要支持达内知道项目进行授权即可
        // 所有客户端只有达内知道项目
        clients.inMemory()  // 将客户端信息保存在服务器内存
                .withClient("knows") //配置达内知道的客户端详情
                // 还需要密码进行客户端验证
                .secret(passwordEncoder.encode("123456"))
                // 配置客户端权限
                .scopes("main") //main可以是随意的字符串
                // 设置这个客户端登录的方式
                // password表示密码登录方式,是框架识别的关键字,不能写错
                .authorizedGrantTypes("password");
    }
    // 重写方法3
    // 这个方法配置资源服务器安全限制
    // 规定哪些资源可以被哪些客户端访问
    // 我们的项目所有客户端可以访问所有资源,所以比较简单
    @Override
    public void configure(AuthorizationServerSecurityConfigurer
                                  security) throws Exception {
        security
                // 允许哪些客户端访问生成令牌的端点(permitAll():全部)
                .tokenKeyAccess("permitAll()")
                // 允许哪些客户端访问验证令牌的端点
                .checkTokenAccess("permitAll()")
                // 允许通过验证的客户端获得令牌
                .allowFormAuthenticationForClients();
    }
}
